package cn.admin.remoting.serialize.impl;

import cn.admin.remoting.Remoting;
import cn.admin.remoting.impl.ConnectionRequest;
import cn.admin.remoting.impl.ConnectionResponse;
import cn.admin.remoting.impl.DefaultClient;
import cn.admin.remoting.impl.DefaultConnection;
import cn.admin.remoting.serialize.CustomSerialization;
import cn.admin.remoting.serialize.DefaultSerialization;
import cn.admin.remoting.serialize.SerializationUtil;
import org.apache.mina.common.IoSession;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ConnResponseSerialization implements CustomSerialization<ConnectionResponse> {

    private static final int IDX_APPTYPE = 9;
    private static final CustomSerialization<Object> defaultSerialize = new DefaultSerialization();

    private static CustomSerialization<Object> getSerial(String type) {
        Remoting.serialUtil();
        CustomSerialization<Object> result = SerializationUtil.getCustom(type);
        if (result == null) {
            result = defaultSerialize;
        }
        return result;
    }

    @Override
    public void serialize(ConnectionResponse response, byte protocol, OutputStream bout) throws IOException {
        Object appObject = response.getAppResponse();
        String appType = null != appObject ? appObject.getClass().getName() : "";
        SliceOutputStream out = (SliceOutputStream)bout;
        byte[] header = new byte[]{13, 1, protocol, 0, 0};
        bout.write(header);
        out.skipInt("connLength");
        bout.write((byte)appType.length());
        out.skipInt("appLength");
        int curSize = out.size();
        protocol = response.getSerializeProtocol();
        defaultSerialize.serialize(response, protocol, out);
        int connLength = out.size() - curSize;
        out.writeSkippedInt("connLength", connLength);
        out.write(appType.getBytes());
        curSize = out.size();
        getSerial(appType).serialize(appObject, protocol, out);
        int appLength = out.size() - curSize;
        out.writeSkippedInt("appLength", appLength);
    }

    @Override
    public ConnectionResponse deserialize(InputStream input, byte protocol) throws IOException {
        return this.deserialize(input,protocol,null);
    }

    public ConnectionResponse deserialize(InputStream is,byte protocol,IoSession session) throws
            DeserializeException {
        ConnectionResponse response = null;

        try {
            byte[] headers = new byte[14];
            is.read(headers);
            int appTypeLen = headers[IDX_APPTYPE] & 255;
            response = (ConnectionResponse) defaultSerialize.deserialize(is,protocol);
            if (0 == appTypeLen) {
                response.setSerializeProtocol(protocol);
                response.setAppResponse((Object)null);
                return response;
            } else {
                byte[] tmp = new byte[appTypeLen];
                is.read(tmp);
                String appType = new String(tmp);
                ClassLoader requestTccl = this.getRequestClassLoader(response, session);
                ClassLoader currentTCCL = Thread.currentThread().getContextClassLoader();
                Object appResponse;
                if (requestTccl != null && requestTccl != currentTCCL) {
                    try {
                        Thread.currentThread().setContextClassLoader(requestTccl);
                        appResponse = getSerial(appType).deserialize(is, protocol);
                    } finally {
                        Thread.currentThread().setContextClassLoader(currentTCCL);
                    }
                } else {
                    appResponse = getSerial(appType).deserialize(is, protocol);
                }

                response.setSerializeProtocol(protocol);
                response.setAppResponse(appResponse);
                return response;
            }
        } catch (IOException e) {
            throw new DeserializeException(e,response);
        }
    }

    private ClassLoader getRequestClassLoader(ConnectionResponse response, IoSession session) {
        if (session == null) {
            return null;
        } else {
            DefaultConnection conn = DefaultConnection.findBySession(session);
            if (conn == null) {
                return null;
            } else {
                DefaultClient client = (DefaultClient) conn.getClient();
                if (client == null) {
                    return null;
                } else {
                    long requestId = response.getRequestId();
                    ConnectionRequest connRequest = client.getPendingRequest(requestId);
                    return connRequest == null ? null : connRequest.getClassLoader();
                }
            }
        }
    }
}
